﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Linq;
using System.Windows.Forms;
using CloudShot.Editor;
using CloudShot.Editor.CanvasFigures;
using CloudShot.Entities;
using CloudShot.Logic;
using CloudShot.Services;
using CloudShot.Utils;
using CloudShot.Utils.Converters;
using CloudShot.Utils.Tweens;

namespace CloudShot.Controls
{
  public sealed partial class CanvasControl : UserControl, IDrawingCanvas, ICursorService
  {
    private readonly GraphicsList _graphicsList = new GraphicsList();
    private readonly Animator _animator;
    private Point _lastMousePoint = Point.Empty;
    private FormHotkeyService _texthotKeyService;

    public Rectangle[] Screens { get; set; }

    public CanvasCrop Crop { get; private set; }

    public GraphicsList GraphicsList
    {
      get { return _graphicsList; }
    }

    public bool CaptureMouse
    {
      get { return Capture; }
      set { Capture = value; }
    }

    public Color FillColor
    {
      get { return Crop.BackColor; }
      set { Crop.BackColor = value; }
    }

    public bool ShowEditorHints { get; set; }

    public CanvasControl()
    {
      InitializeComponent();

      SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
      textBox.Visible = false;

      _texthotKeyService = new FormHotkeyService(textBox);
      _texthotKeyService.RegisterHotkey(AppHotKeys.CommitEditText, () => EndTextEdit(true));
      _texthotKeyService.RegisterHotkey(AppHotKeys.Cancel, () => EndTextEdit(false));

      //TODO replace CaptureForm animator with this this with 
      _animator = new Animator(this);

      //TODO do we need to inject dependency at all?
      Crop = new CanvasCrop(new HtmlHint(_animator))
      {
        IsSelectionState = true,
        BackgroundOpacity = 120,
      };
    }

    public void RefreshCanvas()
    {
      Invalidate();
    }

    public void RefreshCanvas(Rectangle clipRectangle)
    {
      //sometimes when moving figures fast one of the edge will be not drawn, 
      //to prevent it we will little bit increase drawing area
      clipRectangle.Inflate(20, 20);
      Invalidate(clipRectangle);
    }

    public CaptureResult CaptureImage()
    {
      if (Crop.IsSelectionState)
        return CaptureResult.Empty;

      
      List<CanvasObject> selectedObjects = GraphicsList.SelectedObjects.ToList();
      GraphicsList.DeselectAll();
      Crop.IsSelected = false;
      Crop.DrawHint = false;


      Rectangle rect = Crop.Rectangle;
      rect = rect.ApplyPadding(new Padding(1, 1, 0, 0));
      rect.Intersect(ClientRectangle);

      if (rect.Width <= 0 || rect.Height <= 0)
        return CaptureResult.Empty;//TODO: maybe here should be notification about error?


      Bitmap capturedImage;

      //get captured image
      using (var bitmap = new Bitmap(Width, Height))
      {
        DrawToBitmap(bitmap, ClientRectangle);
        capturedImage = bitmap.Clone(rect, PixelFormat.Format32bppArgb);
      }

      selectedObjects.ForEach(so => so.IsSelected = true);
      Crop.IsSelected = true;
      Crop.DrawHint = true;
      return new CaptureResult
      {
        Image = capturedImage,
        Location = PointToScreen(rect.Location)
      };
    }

    public void CancelSelection()
    {
      if (GraphicsList.SelectedObjects.Any())
      {
        Rectangle selectionBoundingBox = GraphicsList.SelectedObjects.GetGroupBoundingBox();
        GraphicsList.DeselectAll();
        RefreshCanvas(selectionBoundingBox);
      }
    }

    public IEnumerable<CanvasObject> GetObjectsAtPoint(Point point)
    {
      return GraphicsList.Where(t => t.HitTest(point) >= 0);
    }

    public void SelectAll()
    {
      GraphicsList.SelectAll();
      RefreshCanvas(GraphicsList.SelectedObjects.GetGroupBoundingBox());
    }

    public void MoveSelection(Point delta)
    {
      if (!GraphicsList.SelectedObjects.Any())
        return;

      var selectedObjects = GraphicsList.SelectedObjects.ToList();

      using (new AutomaticCanvasRefresher(this, selectedObjects.GetGroupBoundingBox))
      {
        foreach (CanvasObject canvasObject in selectedObjects)
        {
          canvasObject.Move(delta.X, delta.Y);
        }
      }
    }

    public void DuplicateSelection()
    {
      if (!GraphicsList.SelectedObjects.Any())
        return;

      using (new AutomaticCanvasRefresher(this, () => GraphicsList.SelectedObjects.GetGroupBoundingBox()))
      {
        var newObjects = GraphicsList.SelectedObjects.Select(o => o.Clone()).ToList();

        GraphicsList.DeselectAll();

        foreach (var canvasObject in newObjects)
        {
          canvasObject.Move(30, 30);
          canvasObject.IsSelected = true;
          GraphicsList.Add(canvasObject);
        }
      }
    }

    public void DeleteSelection()
    {
      Rectangle selectionBoundingBox = GraphicsList.SelectedObjects.GetGroupBoundingBox();

      if (GraphicsList.RemoveSelection())
      {
        RefreshCanvas(selectionBoundingBox);
      }
    }

    public Point GetLastMousePosition()
    {
      return _lastMousePoint;
    }

    public bool IsAnyModifierPressed(KeyModifiers modifiers)
    {
      bool pressed = false;
      if ((modifiers & KeyModifiers.Shift) != 0)
        pressed |= (Control.ModifierKeys & Keys.Shift) != 0;

      if ((modifiers & KeyModifiers.Alt) != 0)
        pressed |= (Control.ModifierKeys & Keys.Alt) != 0;

      if ((modifiers & KeyModifiers.Ctrl) != 0)
        pressed |= (Control.ModifierKeys & Keys.Control) != 0;

      return pressed;
    }

    #region Text Editing

    public event EventHandler TextEditBegin;
    public event EventHandler<TextEditEndEventArgs> TextEditEnd;

    public void BeginTextEdit(CanvasText textFigure)
    {
      textFigure.IsSelected = false;
      Font font = textFigure.GetFont();

      textBox.Multiline = true;
      textBox.Tag = textFigure;
      textBox.Location = textFigure.Rectangle.Location;
      textBox.Size = textFigure.Rectangle.Size;
      textBox.MinimumSize = textFigure.Rectangle.Size;
      textBox.Text = textFigure.Text;
      textBox.Font = font;
      textBox.TextAlign = textFigure.TextAlignment.ToHorizontalAlignment();
      textBox.Visible = true;
      textBox.SelectionStart = 0;
      textBox.SelectionLength = textBox.Text.Length;
      textBox.ArrangeTextBoxSize();

      InvokeTextEditBegin();
      ActiveControl = textBox;
    }

    private void EndTextEdit(bool applyText)
    {
      textBox.Visible = false;

      if (textBox.ContainsFocus)
      {
        textBox.LostFocus -= OnTextBoxLostFocus;
        Focus();
        textBox.LostFocus += OnTextBoxLostFocus;
      }

      var textFigure = textBox.Tag as CanvasText;
      textBox.Tag = null;

      if (textFigure == null)
        return;

      if (applyText)
      {
        textFigure.Text = textBox.Text;
      }

      textFigure.IsSelected = true;
      textFigure.Normalize();

      var rect = textFigure.Rectangle;
      textFigure.Rectangle = new Rectangle(rect.Location, textBox.Size);

      RefreshCanvas(textFigure.GetBoundingBox());
      InvokeTextEditEnd(new TextEditEndEventArgs(applyText));
    }

    private void InvokeTextEditBegin()
    {
      EventHandler handler = TextEditBegin;
      if (handler != null)
        handler(this, EventArgs.Empty);
    }

    private void InvokeTextEditEnd(TextEditEndEventArgs e)
    {
      EventHandler<TextEditEndEventArgs> handler = TextEditEnd;
      if (handler != null)
        handler(this, e);
    }

    #endregion

    #region Hints

    public void ShowHint(string hintText)
    {
      if (!ShowEditorHints)
        return;

      using (new AutomaticCanvasRefresher(this, Crop.GetBoundingBox))
      {
        Crop.Hint.Text = hintText;
        Crop.Hint.Visible = true;
      }
    }

    public void HideHint()
    {
      if (!ShowEditorHints)
        return;

      using (new AutomaticCanvasRefresher(this, Crop.GetBoundingBox))
      {
        Crop.Hint.Visible = false;
      }
    }

    #endregion

    #region Event Handlers

    protected override void OnMouseMove(MouseEventArgs e)
    {
      base.OnMouseMove(e);

      _lastMousePoint = e.Location;
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
      if (textBox.Visible)
        EndTextEdit(true);
      else
      {
        base.OnMouseDown(e);
      }
    }

    protected override void OnLayout(LayoutEventArgs e)
    {
      base.OnLayout(e);

      if (Crop != null)
      {
        //TODO we need to set screens here whet its already added to form. To use RectangleToClient correctly
        Screens = Screen.AllScreens.Select(x => RectangleToClient(x.Bounds)).ToArray();
      }
    }

    protected override void OnResize(EventArgs e)
    {
      base.OnResize(e);

      if (Crop != null)
      {
        Crop.CanvasBounds = Bounds;
      }
    }

    protected override void OnMouseDoubleClick(MouseEventArgs e)
    {
      base.OnMouseDoubleClick(e);

      var location = e.Location;
      CanvasText textFigure = GraphicsList.OfType<CanvasText>().FirstOrDefault(x => x.Rectangle.Contains(location));

      if (textFigure != null)
        BeginTextEdit(textFigure);
    }

    /// <summary>
    ///   Draw graphic objects and
    ///   group selection rectangle (optionally)
    /// </summary>
    private void OnCanvasPaint(object sender, PaintEventArgs e)
    {
      if (DesignMode)
        return;

      Graphics graphics = e.Graphics;

      //graphics.CompositingQuality = CompositingQuality.HighQuality;//do not use, very slow and colors will be different
      graphics.SmoothingMode = SmoothingMode.HighQuality;
      graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
      graphics.PixelOffsetMode = PixelOffsetMode.None;
      graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
      graphics.TextContrast = 0;

      _graphicsList.DrawFigures(graphics);
      Crop.Draw(graphics);

      //TODO try to remove screens as an argument
      _graphicsList.DrawMarkup(graphics, Screens);
      Crop.DrawMarkup(graphics, Screens);

      //for debug, show clip bounds
      //graphics.DrawRectangle(Pens.Red, Rectangle.Inflate(Rectangle.Ceiling(e.Graphics.ClipBounds), -1, -1));
    }

    private void OnTextBoxLostFocus(object sender, EventArgs e)
    {
      EndTextEdit(true);
    }

    #endregion
  }
}